﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Lwm.x10c.Emulator
{
    /// <summary>
    /// Simulates an 0x10c CPU.
    /// </summary>
    public class Cpu
    {
        private WrappedArray ram;
        private WrappedArray regs;
        private uint pc_op;
        private uint pc;
        private ushort sp;
        private ushort o;
        private ushort[] code;

        public TimeSpan CycleLength { get; set; }

        public Cpu(ushort[] code)
        {
            this.ram = new WrappedArray("", 0x10000);

            var regNames = new string[] { "A", "B", "C", "X", "Y", "Z", "I", "J" };
            this.regs = new WrappedArray("reg", 8, regNames);

            this.pc = 0;
            this.sp = 0xffff;
            this.o = 0;
            this.code = code;
        }

        public void Execute()
        {
            for (pc = 0; pc < code.Length;)
            {
                ExecuteInsn();
                System.Threading.Thread.Sleep(this.CycleLength);
            }
        }

        private enum Opcode
        {
            NonBasic = 0x0,
            Set = 0x1,
            Add = 0x2, Sub = 0x3, Mul = 0x4, Div = 0x5, Mod = 0x6,
            Shl = 0x7, Shr = 0x8, And = 0x9, Or = 0xa, Xor = 0xb,
            Ife = 0xc, Ifn = 0xd, Ifg = 0xe, Ifb = 0xf
        }

        private enum NonBasicOpcode
        {
            Reserved = 0x00,
            Jsr = 0x01
        }

        private void ExecuteInsn()
        {
            this.pc_op = pc;
            Opcode opcode = (Opcode)(code[pc] & 0xf);
            byte a = (byte)((code[pc] >> 4) & 0x3f);
            byte b = (byte)((code[pc] >> 10) & 0x3f);
            pc++;

            int bv = Read(b);

            if (opcode == Opcode.NonBasic)
            {
                NonBasicOpcode nbopcode = (NonBasicOpcode)a;

                switch (nbopcode)
                {
                    case NonBasicOpcode.Jsr: // call
                        ram[--sp] = (ushort)pc;
                        pc = (ushort)bv;
                        break;
                    default:
                        throw new Exception("Invalid op!");
                }
                return;
            }

            int res = 0;
            int av = 0;
            if(opcode != Opcode.Set)
                av = Read(a);

            o = 0;

            switch (opcode)
            {
                case Opcode.Set:
                    res = bv;
                    break;
                case Opcode.Add:
                    res = av + bv;
                    if (res > ushort.MaxValue)
                        o = 0x0001;
                    break;
                case Opcode.Sub:
                    res = av - bv;
                    if (res < 0)
                        o = 0xffff;
                    break;
                case Opcode.Mul:
                    res = av * bv;
                    o = (ushort)(((av * bv) >> 16) & 0xffff);
                    break;
                case Opcode.Div:
                    if (bv == 0)
                        res = o = 0;
                    else
                    {
                        res = av / bv;
                        o = (ushort)(((av << 16) / bv) & 0xffff);
                    }
                    break;
                case Opcode.Mod:
                    if (bv == 0)
                        res = 0;
                    else
                        res = av % bv;                    
                    break;
                case Opcode.Shl:
                    res = av << bv;
                    o = (ushort)(((av << bv) >> 16) & 0xffff);
                    break;
                case Opcode.Shr:
                    res = av >> bv;
                    o = (ushort)(((av << 16) >> bv) & 0xffff);
                    break;
                case Opcode.And:
                    res = a & b;
                    break;
                case Opcode.Or:
                    res = a | b;
                    break;
                case Opcode.Xor:
                    res = a ^ b;
                    break;
                case Opcode.Ife:
                    if (av != bv)
                        pc++;
                    break;
                case Opcode.Ifn:
                    if (av == bv)
                        pc++;
                    break;
                case Opcode.Ifg:
                    if (av <= bv)
                        pc++;
                    break;
                case Opcode.Ifb:
                    if (!((av & bv) != 0))
                        pc++;
                    break;
                default:
                    throw new Exception("Invalid op!");
            }

            Write(a, res);
        }

        private ushort Read(byte op)
        {
            if (op >= 0x00 && op <= 0x07) // Register
            {
                return regs[op];
            }
            else if (op >= 0x08 && op <= 0x0f) // Register indirect
            {
                return ram[regs[op - 0x08]];
            }
            else if (op >= 0x10 && op <= 0x17) // Register indirect + offset
            {
                ushort adr = code[pc++];
                return ram[adr + regs[op - 0x10]];
            }
            else if (op >= 0x20) // literal
            {
                return (ushort)(op - 0x20);
            }
            else
            {
                switch (op)
                {
                    case 0x18: // Pop
                        return ram[sp++];
                    case 0x19: // Peek
                        return ram[sp];
                    case 0x1a: // Push
                        return ram[--sp];
                    case 0x1b: // Sp
                        return sp;
                    case 0x1c: // Pc
                        return (ushort)pc_op;
                    case 0x1d: // O
                        return o;
                    case 0x1e: // next word
                        return ram[code[pc++]];
                    case 0x1f: // next word literal
                        return code[pc++];
                    default:
                        throw new NotImplementedException();
                }
            }
        }

        private void Write(byte op, int value)
        {
            Write(op, (ushort)value);
        }

        private void Write(byte op, ushort value)
        {
            if (op >= 0x00 && op <= 0x07) // Register
            {
                regs[op] = value;
            }
            else if (op >= 0x08 && op <= 0x0f) // Register indirect
            {
                ram[regs[op - 0x08]] = value;
            }
            else if (op >= 0x10 && op <= 0x17) // Register indirect + offset
            {
                ushort adr = code[pc++];
                ram[adr + regs[op - 0x10]] = value;
            }
            else if (op >= 0x20) // literal
            {
                // do nothing
            }
            else
            {
                switch (op)
                {
                    case 0x18: // Pop
                        ram[sp++] = value;
                        break;
                    case 0x19: // Peek
                        ram[sp] = value;
                        break;
                    case 0x1a: // Push
                        ram[--sp] = value;
                        break;
                    case 0x1b: // Sp
                        sp = value;
                        Console.WriteLine("SP = " + value);
                        break;
                    case 0x1c: // Pc
                        pc = value;
                        Console.WriteLine("PC = " + value);
                        break;
                    case 0x1d: // O
                        o = value;
                        Console.WriteLine("O = " + value);
                        break;
                    case 0x1e: // next word
                        ram[code[pc++]] = value;
                        break;
                    case 0x1f: // next word literal
                        // do nothing
                        break;
                    default:
                        throw new NotImplementedException();
                }
            }
        }
    }
}
